//
//  KeyboardManager.swift
//  Risac
//
//  Created by remy on 2018/7/16.
//

import UIKit

/// 响应者是否在监听范围
private var kbIdleResponderKey: Void?
/// 响应者所属的监听者key
private var kbObserverKey: Void?
/// 监听者是否开启监听key
private var kbNotifyEnabledKey: Void?

/// 键盘通知管理者
public class KeyboardManager {
    /// 单例
    public static let shared = KeyboardManager()
    /// 当前最上层监听者
    private weak var topKBObserver: RisacKeyboardObservable?
    /// 是否开启键盘事件监听,默认关闭
    public var enabled: Bool = false {
        willSet {
            NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
            NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
            NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidShowNotification, object: nil)
            NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidHideNotification, object: nil)
            if newValue {
                NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
                NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
                NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(_:)), name: UIResponder.keyboardDidShowNotification, object: nil)
                NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidHide(_:)), name: UIResponder.keyboardDidHideNotification, object: nil)
            }
        }
    }
    
    private init() {}
    
    private func isNotifyEnabled(_ target: RisacKeyboardObservable) -> Bool {
        return objc_getAssociatedObject(target, &kbNotifyEnabledKey) as? Bool ?? true
    }
    
    private func isIdleResponder(_ target: UIResponder) -> Bool {
        return objc_getAssociatedObject(target, &kbIdleResponderKey) as? Bool ?? false
    }
    
    private func curveMap(_ curve: UIView.AnimationCurve) -> UIView.AnimationOptions {
        switch curve {
        case .easeIn:
            return .curveEaseIn
        case .easeInOut:
            return .curveEaseInOut
        case .easeOut:
            return .curveEaseOut
        default:
            return .curveLinear
        }
    }
}

extension KeyboardManager {
    @objc func keyboardWillShow(_ notification: Notification) {
        self.topKBObserver = nil
        guard let firstResponder = UIApplication.firstResponder else { return }
        if let target = objc_getAssociatedObject(firstResponder, &kbObserverKey) as? RisacKeyboardObservable {
            // 找出当前响应者保存的键盘监听者
            self.topKBObserver = target
        } else {
            // 如果当前响应者没有保存键盘监听者,判断是否是普通元素
            guard !isIdleResponder(firstResponder) else { return }
            var res = firstResponder
            while true {
                if let res = res as? RisacKeyboardObservable {
                    // 找出离当前响应者最近的键盘监听者并保存
                    objc_setAssociatedObject(firstResponder, &kbObserverKey, res, .OBJC_ASSOCIATION_ASSIGN)
                    self.topKBObserver = res
                    break
                }
                guard let next = res.next else {
                    // 如果没有最近的键盘监听者则标记为普通元素
                    objc_setAssociatedObject(firstResponder, &kbIdleResponderKey, true, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                    break
                }
                res = next
            }
        }
        animateKeyboard(notification, appearing: true)
    }
    @objc func keyboardWillHide(_ notification: Notification) {
        animateKeyboard(notification, appearing: false)
    }
    @objc func keyboardDidShow(_ notification: Notification) {
        guard let observer = self.topKBObserver, isNotifyEnabled(observer) else { return }
        guard let frame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
        observer.keyboardDidAppear(animated: false, frame: frame)
    }
    @objc func keyboardDidHide(_ notification: Notification) {
        guard let observer = self.topKBObserver, isNotifyEnabled(observer) else { return }
        guard let frame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
        observer.keyboardDidDisappear(animated: false, frame: frame)
    }
    func animateKeyboard(_ notification: Notification, appearing: Bool) {
        guard let observer = self.topKBObserver else { return }
        guard let info = notification.userInfo, let frame = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, let curveValue = info[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int, let curve = UIView.AnimationCurve(rawValue: curveValue), let duration = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval else { return }
        UIView.animate(withDuration: duration, delay: 0.0, options: [curveMap(curve)]) { [weak self] in
            guard let self = self else { return }
            if self.isNotifyEnabled(observer) {
                if appearing {
                    observer.keyboardWillAppear(animated: true, frame: frame)
                } else {
                    observer.keyboardWillDisappear(animated: true, frame: frame)
                }
            }
        }
        UIView.beginAnimations(nil, context: nil)
        UIView.setAnimationCurve(curve)
        UIView.setAnimationDuration(duration)
        if isNotifyEnabled(observer) {
            if appearing {
                observer.keyboardWillAppear(animated: true, frame: frame)
            } else {
                observer.keyboardWillDisappear(animated: true, frame: frame)
            }
        }
        UIView.commitAnimations()
    }
}

public protocol RisacKeyboardObservable: RisacCompatible where Self: UIResponder {
    func keyboardWillAppear(animated: Bool, frame: CGRect)
    func keyboardWillDisappear(animated: Bool, frame: CGRect)
    func keyboardDidAppear(animated: Bool, frame: CGRect)
    func keyboardDidDisappear(animated: Bool, frame: CGRect)
}
public extension RisacKeyboardObservable {
    func keyboardWillAppear(animated: Bool, frame: CGRect) {}
    func keyboardWillDisappear(animated: Bool, frame: CGRect) {}
    func keyboardDidAppear(animated: Bool, frame: CGRect) {}
    func keyboardDidDisappear(animated: Bool, frame: CGRect) {}
}

extension RisacSpace where Base: RisacKeyboardObservable {
    /// 是否开启键盘监听
    public var kbNotifyEnabled: Bool {
        get {
            return (objc_getAssociatedObject(base, &kbNotifyEnabledKey) as? Bool) ?? true
        }
        set {
            objc_setAssociatedObject(base, &kbNotifyEnabledKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    /// 设置调用该方法的视图为响应者view的键盘监听者,默认为离当前响应者最近的键盘监听者
    public func bindResponder(by view: UIResponder) {
        objc_setAssociatedObject(view, &kbObserverKey, base, .OBJC_ASSOCIATION_ASSIGN)
    }
}
